package com.springboot.service.aop;

/**
 * @Author: luobendexiaoqiang
 * @Date: 2020/7/28 0:03
 */

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.RateLimiter;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.*;
import java.util.regex.Pattern;


@Component("nameInterceptor")
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class NameInterceptor implements Interceptor {

    private static final Log log = LogFactory.getLog(NameInterceptor.class);

    @Value("${environment:#{null}}")
    private String environment;

    // 追加标记
    private static final String TAG = "【测试购买无效】";

    private List<String> MYSQLTABLECONSTANT = Lists.newArrayList("GROUP", "LIMIT", "ORDER", "WHERE");

    private Map<String, String> tableMap = Maps.newHashMap();

    private Map<String, String> entityMap = Maps.newHashMap();

    public void setTableMap(Map<String, String> tableMap) {
        this.tableMap = tableMap;
    }

    public void setEntityMap(Map<String, String> entityMap) {
        this.entityMap = entityMap;
    }

    @Override
    public Object plugin(Object target) {

        // 当目标类是StatementHandler类型时，才包装目标类，否则直接返回目标本身,减少目标被代理的次数
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
        // 分离代理对象链
        // 由于目标类可能被多个插件拦截，从而形成多次代理，通过下面的两次循环可以分离出最原始的的目标类
        while (metaStatementHandler.hasGetter("h")) {
            Object object = metaStatementHandler.getValue("h");
            metaStatementHandler = SystemMetaObject.forObject(object);
        }
        // 分离最后一个代理对象的目标类
        while (metaStatementHandler.hasGetter("target")) {
            Object object = metaStatementHandler.getValue("target");
            metaStatementHandler = SystemMetaObject.forObject(object);
        }

        MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();

        // 只拦截 SELECT/UPDATE 方法
        if (!SqlCommandType.SELECT.equals(sqlCommandType)
                && !SqlCommandType.UPDATE.equals(sqlCommandType)) {
            return invocation.proceed();
        }

        // 获取到原始sql语句
        BoundSql boundSql = statementHandler.getBoundSql();
        String sql = boundSql.getSql().replaceAll("\\s+", " ");
        Object parameterObject = boundSql.getParameterObject();


        // 只拦截sql中包含了拦截规则中设置的表和字段的语句
        boolean flag = false;
        for (String table : tableMap.keySet()) {
            if (sql.contains(table) && sql.contains(tableMap.get(table))) {
                flag = true;
                break;
            }
        }
        if (!flag) {
            return invocation.proceed();
        }

        String method = mappedStatement.getId();
        log.info("method:" + method + ", type:" + sqlCommandType.toString());

        if (SqlCommandType.SELECT.equals(sqlCommandType)) {
            String newSql = select(sql);
            Field field = boundSql.getClass().getDeclaredField("sql");
            field.setAccessible(true);
            field.set(boundSql, newSql);
        } else {
            Object newParam = update(parameterObject);
            Field field = boundSql.getClass().getDeclaredField("parameterObject");
            field.setAccessible(true);
            field.set(boundSql, newParam);
        }
        return invocation.proceed();
    }

    /**
     * @param sql SELECT p.peo_name, p.peo_sex FROM people p
     * tableName    表的别名 p
     * columnName   拦截的字段名 peo_name
     * fullName     sql中拼接完成的字段名 p.peo_name
     * @return SELECT CONCAT(TAG,p.peo_name) AS peo_name, p.peo_sex FROM people p
     */
    private String select(String sql) {
        String[] sqls = Arrays.stream(sql.split(" ")).filter(StringUtils::isNotBlank).toArray(String[]::new);
        try {
            for (int i = 0; i < sqls.length - 1; i++) {
                if (tableMap.containsKey(sqls[i])) {
                    String tableName = sqls[i + 1].toUpperCase();
                    String columnName;
                    String fullName;
                    String regex;
                    boolean needAs = false;
                    if (MYSQLTABLECONSTANT.contains(tableName)) {
                        columnName = tableMap.get(sqls[i]);
                        regex = "`?" + columnName + "`?";
                        fullName = columnName;
                    } else {
                        if (tableName.equals("AS")) {
                            tableName = sqls[i + 2];
                        } else {
                            tableName = sqls[i + 1];
                        }
                        columnName = tableMap.get(sqls[i]);
                        regex = tableName + "\\." + "`?" + columnName + "`?";
                        fullName = tableName + "." + columnName;
                    }
                    String asName = " AS " + columnName;
                    for (int j = 0; j < i; j++) {
                        if (Pattern.compile(regex, Pattern.CASE_INSENSITIVE).matcher(sqls[j]).find()) {
                            if (Pattern.compile(regex + ",", Pattern.CASE_INSENSITIVE).matcher(sqls[j]).find() ||
                                    (Pattern.compile(regex + "$", Pattern.CASE_INSENSITIVE).matcher(sqls[j]).find() &&
                                            ("FROM".equalsIgnoreCase(sqls[j + 1]) || sqls[j + 1].startsWith(",")))){
                                needAs = true;
                            }
                            boolean start = sqls[j].startsWith(",");
                            boolean end = sqls[j].endsWith(",");
                            String[] column = Arrays.stream(sqls[j].split(",")).filter(StringUtils::isNotBlank).toArray(String[]::new);
                            for (int k = 0; k < column.length; k++) {
                                if (Pattern.compile(regex, Pattern.CASE_INSENSITIVE).matcher(column[k]).matches()) {
                                    column[k] = "CONCAT(\"" + TAG + "\"," + fullName + ")";
                                    if (needAs) {
                                        column[k] += asName;
                                    }
                                }
                                sqls[j] = String.join(",", column);
                                if (start) {
                                    sqls[j] = "," + sqls[j];
                                }
                                if (end) {
                                    sqls[j] = sqls[j] + ",";
                                }
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            log.error("fix sql error: ", e);
            return sql;
        }
        return String.join(" ", sqls);
    }

    /**
     * @param parameterObject 在保存到数据库时，去掉拦截字段的TAG标志
     * @return parameterObject
     */
    private Object update(Object parameterObject) {
        try {
            Class clz = parameterObject.getClass();
            String fieldName = entityMap.get(clz.getName());
            if(StringUtils.isEmpty(fieldName)){
                return parameterObject;
            }
            Field field = clz.getDeclaredField(fieldName);
            field.setAccessible(true);
            Object value = field.get(parameterObject);
            if (value instanceof String) {
                String name = (String) value;
                if (StringUtils.isNotEmpty(name) && name.startsWith(TAG)){
                    field.set(parameterObject, name.substring(TAG.length()));
                }
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            log.error("fix parameter error: ", e);
        }
        return parameterObject;
    }

    @Override
    public void setProperties(Properties properties) {
    }


}
